﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using System.Drawing.Drawing2D;

namespace SheetLib
{
    public class Connector:IDisposable
    {
        public const int connectorLineAreaWidth = 5;

        public delegate void ConnectorPaintEventHandler(Connector sender, Rectangle e);
        public delegate void ConnectorDesposedEventHandler(Connector sender);
        public event ConnectorPaintEventHandler Repainting;
        public event ConnectorDesposedEventHandler Disposed;

        private List<Point> handledPoints = new List<Point>();
        private SheetControl OwnerSheet;
        private ConnectedElement ce1, ce2;
        private bool selected;

        public Connector(ConnectedElement ce1, ConnectedElement ce2, SheetControl ownerSheet)
        {
            this.ce1 = ce1;
            this.ce2 = ce2;
            this.OwnerSheet = ownerSheet;
            CallbackCreated();
        }

        public Connector(BaseControl control1, ConnectionPlace place1, BaseControl control2,ConnectionPlace place2, SheetControl ownerSheet)
        {
            this.ce1 = new ConnectedElement(control1, place1);
            this.ce2 = new ConnectedElement(control2, place2);
            this.OwnerSheet = ownerSheet;
            CallbackCreated();
        }

        private void CallbackCreated()
        {
            ce1.control.AddAssociatedConnector(ce1.place, this);
            ce2.control.AddAssociatedConnector(ce2.place, this);
        }

        public Point[] Points
        {
            get
            {
                List<Point> all = new List<Point>();
                all.Add(GetFirstPoint());
                Point pt = GetLastPoint();
                all.AddRange(handledPoints);
                all.Add(pt);
                return all.ToArray();
            }
        }

        private Point GetFirstPoint()
        {
            Point pt = ce1.ConnectionPoint;
            if (pt!=ce1.LastConnPoint)
            {
                //conCount = 0;
                handledPoints = Connect(ce1.ConnectionPoint, ce2.ConnectionPoint, this.OwnerSheet,
                    this.ce1.control,this.ce2.control);
                ce1.LastConnPoint = pt;
            }
            return pt;
        }

        private Point GetLastPoint()
        {
            Point pt = ce2.ConnectionPoint;
            if (pt!=ce2.LastConnPoint)
            {
                //conCount = 0;
                handledPoints = Connect(ce1.ConnectionPoint, ce2.ConnectionPoint, this.OwnerSheet,
                    this.ce1.control, this.ce2.control);
                ce1.LastConnPoint = pt;
            }
            return pt;
        }

        public void Connect()
        {
            //conCount = 0;
            this.handledPoints = Connect(ce1.ConnectionPoint, ce2.ConnectionPoint, this.OwnerSheet,
                    this.ce1.control, this.ce2.control);
            if (Repainting != null)
            {
                Repainting(this, ClipRectangle);
            }
        }

        public Rectangle ClipRectangle
        {
            get
            {
                Point[] pts = Points;
                Rectangle rect = new Rectangle(pts.Min(p => p.X) - 5, pts.Min(p => p.Y) - 5, 0, 0);
                rect.Width = pts.Max(p => p.X) - rect.X + 10;
                rect.Height = pts.Max(p => p.Y) - rect.Y + 10;
                return rect;
            }
        }

        public GraphicsPath ConnectorArea
        {
            get
            {
                GraphicsPath gp = new GraphicsPath();
                Point[] pts = Points;
                for (int i = 0; i < pts.Length - 1; i++)
                {
                    Point pt1 = pts[i];
                    Point pt2 = pts[i + 1];
                    if (pt1.X > pt2.X)
                    {
                        Point pt = pt1;
                        pt1 = pt2;
                        pt2 = pt;
                    }
                    if (pt1.X == pt2.X)
                    {
                        if (pt1.Y < pt2.Y)
                        {
                            gp.AddRectangle(new Rectangle(pt1.X - connectorLineAreaWidth / 2, pt1.Y,
                                connectorLineAreaWidth, pt2.Y - pt1.Y));
                        }
                        else
                        {
                            gp.AddRectangle(new Rectangle(pt1.X - connectorLineAreaWidth / 2, pt2.Y,
                                connectorLineAreaWidth, pt1.Y - pt2.Y));
                        }
                    }
                    else
                    {
                        gp.AddRectangle(new Rectangle(pt1.X, pt1.Y - connectorLineAreaWidth / 2,
                                pt2.X - pt1.X, connectorLineAreaWidth));
                    }
                }
                return gp;
            }
        }

        //public static int conCount = 0;
        //public static int pointC = 0;

        public static List<Point> Connect(Point point1, Point point2, SheetControl ownerSheet,BaseControl ignoredC1,BaseControl ignoredC2)
        {
            //conCount++;
            //if (conCount > 70) System.Windows.Forms.MessageBox.Show(conCount.ToString() + "\n" + point1 + " " + point2);
            List<BaseControl> tempList = new List<BaseControl>();
            BaseControl control;
            Rectangle r, r2, inters;
            bool needToReverse = false;
            List<Point> result = new List<Point>();
            if (point1.X > point2.X)
            {
                Point temp = point1;
                point1 = point2;
                point2 = temp;
                needToReverse = true;
            }
            if (point1.X == point2.X)
            {
                for (int i = 0; i < ownerSheet.CanvasControls.Count; i++)
                {
                }
            }
            else if (point1.Y == point2.Y)
            {
                tempList.Clear();
                r = new Rectangle(point1.X, point1.Y - 1 - connectorLineAreaWidth / 2, point2.X - point1.X, connectorLineAreaWidth);
                for (int i = 0; i < ownerSheet.CanvasControls.Count; i++)
                {
                    control = ownerSheet.CanvasControls[i];
                    if (control != ignoredC1 && control != ignoredC2)
                    {
                        r2 = new Rectangle(control.Location, control.Size);
                        inters = Rectangle.Intersect(r, r2);
                        if (inters.Height != 0 && inters.Width != 0)
                        {
                            tempList.Add(control);
                        }
                    }
                }
                if (tempList.Count > 0)
                {
                    control = tempList.OrderBy(ctrl => ctrl.Location.X).ToArray()[0];
                    result.Add(new Point(control.Location.X - 1 - connectorLineAreaWidth / 2, point1.Y));
                    result.Add(new Point(control.Location.X - 1 - connectorLineAreaWidth / 2, control.Bottom + 1 + connectorLineAreaWidth / 2));
                    result.AddRange(Connect(result[result.Count - 1], point2, ownerSheet,ignoredC1,ignoredC2));
                    if (needToReverse) result.Reverse();
                    return result;
                }
            }
            else if (point1.Y > point2.Y)
            {
                tempList.Clear();
                r = new Rectangle(point1.X-1-connectorLineAreaWidth/2, point2.Y, connectorLineAreaWidth, point1.Y - point2.Y);
                for (int i = 0; i < ownerSheet.CanvasControls.Count; i++)
                {
                    control = ownerSheet.CanvasControls[i];
                    if (control != ignoredC1 && control != ignoredC2)
                    {
                        r2 = new Rectangle(control.Location, control.Size);
                        inters = Rectangle.Intersect(r, r2);
                        if (inters.Height != 0 && inters.Width != 0)
                        {
                            tempList.Add(control);
                        }
                    }
                }
                if (tempList.Count > 0)
                {
                    control = tempList.OrderByDescending(ctrl => ctrl.Location.Y).ToArray()[0];
                    result.Add(new Point(point1.X, control.Bottom + 1 + connectorLineAreaWidth / 2));
                    result.Add(new Point(control.Right + 1 + connectorLineAreaWidth / 2, control.Bottom + 1 + connectorLineAreaWidth / 2));
                    result.AddRange(Connect(result[result.Count - 1], point2, ownerSheet,ignoredC1,ignoredC2));
                    if (needToReverse) result.Reverse();
                    return result;
                }
                result.Add(new Point(point1.X, point2.Y));
                r = new Rectangle(point1.X, point2.Y - 1 - connectorLineAreaWidth / 2,
                    point2.X - point1.X, connectorLineAreaWidth);
                tempList.Clear();
                for (int i = 0; i < ownerSheet.CanvasControls.Count; i++)
                {
                    control = ownerSheet.CanvasControls[i];
                    if (control != ignoredC1 && control != ignoredC2)
                    {
                        r2 = new Rectangle(control.Location, control.Size);
                        inters = Rectangle.Intersect(r, r2);
                        if (inters.Height != 0 && inters.Width != 0)
                        {
                            tempList.Add(control);
                        }
                    }
                }
                if (tempList.Count > 0)
                {
                    control = tempList.OrderBy(ctrl => ctrl.Location.X).ToArray()[0];
                    Point testPt = new Point(control.Location.X - 1 - connectorLineAreaWidth / 2, control.Bottom + 1 + connectorLineAreaWidth / 2);
                    if (testPt != point1)
                    {
                        result.Add(new Point(control.Location.X - 1 - connectorLineAreaWidth / 2, result[result.Count - 1].Y));
                        result.Add(testPt);
                        result.AddRange(Connect(result[result.Count - 1], point2, ownerSheet,ignoredC1,ignoredC2));
                        if (needToReverse) result.Reverse();
                        return result;
                    }
                    else
                    {
                        result.Clear();
                        tempList.Clear();
                        r = new Rectangle(point1.X, point1.Y - 1 - connectorLineAreaWidth / 2,
                            point2.X - point1.X, connectorLineAreaWidth);
                        for (int i = 0; i < ownerSheet.CanvasControls.Count; i++)
                        {
                            control = ownerSheet.CanvasControls[i];
                            if (control != ignoredC1 && control != ignoredC2)
                            {
                                r2 = new Rectangle(control.Location, control.Size);
                                inters = Rectangle.Intersect(r, r2);
                                if (inters.Height != 0 && inters.Width != 0)
                                {
                                    tempList.Add(control);
                                }
                            }
                        }
                        if (tempList.Count > 0)
                        {
                            control = tempList.OrderBy(ctrl => ctrl.Location.X).ToArray()[0];
                            result.Add(new Point(control.Location.X - 1 - connectorLineAreaWidth / 2, point1.Y));
                            result.Add(new Point(control.Location.X - 1 - connectorLineAreaWidth / 2, control.Bottom + 1 + connectorLineAreaWidth / 2));
                            result.AddRange(Connect(result[result.Count - 1], point2, ownerSheet,ignoredC1,ignoredC2));
                            if (needToReverse) result.Reverse();
                            return result;
                        }
                        result.Add(new Point(point2.X, point1.Y));
                        r = new Rectangle(point2.X - 1 - connectorLineAreaWidth / 2, point2.Y,
                            connectorLineAreaWidth, point1.Y - point2.Y);
                        tempList.Clear();
                        for (int i = 0; i < ownerSheet.CanvasControls.Count; i++)
                        {
                            control = ownerSheet.CanvasControls[i];
                            if (control != ignoredC1 && control != ignoredC2)
                            {
                                r2 = new Rectangle(control.Location, control.Size);
                                inters = Rectangle.Intersect(r, r2);
                                if (inters.Height != 0 && inters.Width != 0)
                                {
                                    tempList.Add(control);
                                }
                            }
                        }
                        if (tempList.Count > 0)
                        {
                            control = tempList.OrderByDescending(ctrl => ctrl.Location.Y).ToArray()[0];
                            result.Add(new Point(point2.X, control.Bottom+1+connectorLineAreaWidth/2));
                            result.Add(new Point(control.Right + 1 + connectorLineAreaWidth / 2, control.Bottom + 1 + connectorLineAreaWidth / 2));
                            result.AddRange(Connect(result[result.Count - 1], point2, ownerSheet,ignoredC1,ignoredC2));
                            if (needToReverse) result.Reverse();
                            return result;
                        }
                    }
                }
            }
            else if (point1.Y < point2.Y)
            {
                tempList.Clear();
                r = new Rectangle(point1.X-1-connectorLineAreaWidth/2, point1.Y, connectorLineAreaWidth, point2.Y - point1.Y);
                for (int i = 0; i < ownerSheet.CanvasControls.Count; i++)
                {
                    control = ownerSheet.CanvasControls[i];
                    if (control != ignoredC1 && control != ignoredC2)
                    {
                        r2 = new Rectangle(control.Location, control.Size);
                        inters = Rectangle.Intersect(r, r2);
                        if (inters.Height != 0 && inters.Width != 0)
                        {
                            tempList.Add(control);
                        }
                    }
                }
                if (tempList.Count > 0)
                {
                    control = tempList.OrderBy(ctrl => ctrl.Location.Y).ToArray()[0];
                    result.Add(new Point(point1.X, control.Location.Y - 1 - connectorLineAreaWidth / 2));
                    result.Add(new Point(control.Right + 1 + connectorLineAreaWidth / 2, control.Location.Y - 1 - connectorLineAreaWidth / 2));
                    result.AddRange(Connect(result[result.Count - 1], point2, ownerSheet,ignoredC1,ignoredC2));
                    if (needToReverse) result.Reverse();
                    return result;
                }
                result.Add(new Point(point1.X, point2.Y));
                r = new Rectangle(point1.X, point2.Y - 1 - connectorLineAreaWidth / 2,
                    point2.X - point1.X, connectorLineAreaWidth);
                tempList.Clear();
                for (int i = 0; i < ownerSheet.CanvasControls.Count; i++)
                {
                    control = ownerSheet.CanvasControls[i];
                    if (control != ignoredC1 && control != ignoredC2)
                    {
                        r2 = new Rectangle(control.Location, control.Size);
                        inters = Rectangle.Intersect(r, r2);
                        if (inters.Height != 0 && inters.Width != 0)
                        {
                            tempList.Add(control);
                        }
                    }
                }
                if (tempList.Count > 0)
                {
                    control = tempList.OrderBy(ctrl => ctrl.Location.X).ToArray()[0];
                    Point testPt = new Point(control.Location.X - 1 - connectorLineAreaWidth / 2, control.Bottom + 1 + connectorLineAreaWidth / 2);
                    if (testPt != point1)
                    {
                        result.Add(new Point(control.Location.X - 1 - connectorLineAreaWidth / 2, result[result.Count - 1].Y));
                        result.Add(testPt);
                        result.AddRange(Connect(result[result.Count - 1], point2, ownerSheet,ignoredC1,ignoredC2));
                        if (needToReverse) result.Reverse();
                        return result;
                    }
                    else
                    {
                        result.Clear();
                        tempList.Clear();
                        r = new Rectangle(point1.X, point1.Y - 1 - connectorLineAreaWidth / 2,
                            point2.X - point1.X, connectorLineAreaWidth);
                        for (int i = 0; i < ownerSheet.CanvasControls.Count; i++)
                        {
                            control = ownerSheet.CanvasControls[i];
                            if (control != ignoredC1 && control != ignoredC2)
                            {
                                r2 = new Rectangle(control.Location, control.Size);
                                inters = Rectangle.Intersect(r, r2);
                                if (inters.Height != 0 && inters.Width != 0)
                                {
                                    tempList.Add(control);
                                }
                            }
                        }
                        if (tempList.Count > 0)
                        {
                            control = tempList.OrderBy(ctrl => ctrl.Location.X).ToArray()[0];
                            result.Add(new Point(control.Location.X - 1 - connectorLineAreaWidth / 2, point1.Y));
                            result.Add(new Point(control.Location.X - 1 - connectorLineAreaWidth / 2, control.Bottom + 1 + connectorLineAreaWidth / 2));
                            result.AddRange(Connect(result[result.Count - 1], point2, ownerSheet,ignoredC1,ignoredC2));
                            if (needToReverse) result.Reverse();
                            return result;
                        }
                        result.Add(new Point(point2.X, point1.Y));
                        r = new Rectangle(point2.X - 1 - connectorLineAreaWidth / 2, point1.Y,
                            connectorLineAreaWidth, point2.Y - point1.Y);
                        tempList.Clear();
                        for (int i = 0; i < ownerSheet.CanvasControls.Count; i++)
                        {
                            control = ownerSheet.CanvasControls[i];
                            if (control != ignoredC1 && control != ignoredC2)
                            {
                                r2 = new Rectangle(control.Location, control.Size);
                                inters = Rectangle.Intersect(r, r2);
                                if (inters.Height != 0 && inters.Width != 0)
                                {
                                    tempList.Add(control);
                                }
                            }
                        }
                        if (tempList.Count > 0)
                        {
                            control = tempList.OrderBy(ctrl => ctrl.Location.Y).ToArray()[0];
                            result.Add(new Point(point2.X, control.Location.Y - 1 - connectorLineAreaWidth / 2));
                            result.Add(new Point(control.Right + 1 + connectorLineAreaWidth / 2, control.Location.Y - 1 - connectorLineAreaWidth / 2));
                            result.AddRange(Connect(result[result.Count - 1], point2, ownerSheet,ignoredC1,ignoredC2));
                            if (needToReverse) result.Reverse();
                            return result;
                        }
                    }
                }
            }
            if (needToReverse) result.Reverse();
            return result;
        }

        internal ConnectedElement CE1
        {
            get { return ce1; }
        }
        internal ConnectedElement CE2
        {
            get { return ce2; }
        }
        public bool Selected
        {
            get { return selected; }
            set { selected = value; }
        }

        #region IDisposable Members

        public void Dispose()
        {
            if (Disposed != null)
            {
                Disposed(this);
            }
            this.ce1 = ConnectedElement.Empty;
            this.ce2 = ConnectedElement.Empty;
            this.OwnerSheet = null;
        }

        #endregion
    }

    public struct ConnectedElement
    {
        public BaseControl control;
        public ConnectionPlace place;
        public Point LastConnPoint;

        public ConnectedElement(BaseControl control, ConnectionPlace cp)
        {
            this.control = control;
            this.place = cp;
            this.LastConnPoint = Point.Empty;
        }

        public Point ConnectionPoint
        {
            get
            {
                return control.GetConnectionPoint(place);
            }
        }

        public static implicit operator ConnectedElement(HotSpot hs)
        {
            return new ConnectedElement(hs.Control, hs.ConnectionPlace);
        }
        public static ConnectedElement Empty = new ConnectedElement();
    }
    public enum ConnectionPlace
    {
        Right,
        Down,
        Left,
        Up
    }
}
